--[[
    子游戏模块，用于创建子游戏，管理已加载的模块，并在游戏退出时正确释放
]] --

local blaze = require "blaze"
local app = require "app"
local gamelib = require "club.threecard.gamelib"
local log = app.logging.get("game")

local M = {}

local function setup_screen_orientation(config)
    if config.orientation == 1 then
        app.screen.landscape()
    elseif config.orientation == 2 then
        app.screen.portrait()
    end
end

local function wait_for_avatar()
    return blaze.promise.new(function(resolve, reject)
        app.coroutine.start(function()
            while true do
                coroutine.yield()
                local player = app.kbe.player()
                if not player or player.className == "Avatar" then
                    resolve()
                    return
                end
            end
        end)
    end)
end

local function add_i18n(lang, package, package_path)
    local db = blaze.config.load(string.format("%s/i18n/%s.lua", package_path, lang)) or {}
    local styles = nil
    local styles_path = string.format("%s.config.i18n_styles", package)
    if blaze.package_exists(styles_path) then
        styles = require(styles_path)
    end
    app.i18n.add(package, db, styles)
end

local function app_internal_start(self)
    local package = self.package
    self.log = self.logging.get(package)
    local config = app.config.games.find_with_package(package)
    if not config then
        error(string.format("找不到子游戏配置：%s", package))
    end
    setup_screen_orientation(config)

    local package_path = package:gsub("%.","/")
    local bank = app.audio.create_bank(app, package, {
        actions = require("blaze.audio.actions"),
        soundbank = blaze.config.load(string.format("%s/soundbank.lua", package_path)) or {}
    })
    app.audio.add_bank(bank)

    local lang = app.store.setting.state.lang
    add_i18n(lang, "club.threecard.gamelib", "club/threecard/gamelib")
    add_i18n(lang, package, package_path)

    for name, def in pairs(self.libs) do
        assert(type(def), "string")
        local ok, result = pcall(require, def)
        if not ok then
            error(string.format("加载公共库失败：%s %s", name, result))
        end

        local lib = result
        ok, result = pcall(lib.start, lib)
        if not ok or not result then
            error(string.format("启动公共库失败：%s %s", def, result))
        end
        self[name] = lib
    end

    local chat = self.chat
end

local function app_start_co(self, resolve, reject)
    self.log.info(string.format("启动游戏 %s ...", self.package))
    local ok = xpcall(function()
        app_internal_start(self)
        self:on_start()
        self:on_started()
        resolve()
    end, function(e)
        reject(e)
    end)
    if ok then
        self.log.info("启动游戏成功")
    end
end

local function app_start(self)
    self.exited = false
    return blaze.promise.new(function(resolve, reject)
        app.coroutine.start(app_start_co, self, resolve, reject)
    end):catch(function(e)
        self.log.error(string.format("启动游戏失败：%s", e))
        error(e)
    end)
end

local function app_on_start()
end

local function app_on_started(self)
end

local function send_exit_game(self)
    local player = self.kbe.player()
    if not player then
        return blaze.promise.resolve()
    end
    
    local network_valid = self.kbe.is_network_valid()
    if not network_valid then
        return blaze.promise.resolve()
    end

    if player.className == "Avatar" then
        log.info(string.format("请求退出游戏 %s ...", self.package))
        return player:exit_game()
    else
        log.warn(string.format("异常退出游戏，尝试修正状态 id=%d player=%s", player.id, player.className))
        return player.room_api:leave(1)
            :next(function(resolve, reject)
                return wait_for_avatar()
            end)
            :next(function()
                player = self.kbe.player()
                if player then
                    return player:exit_game()
                else
                    return blaze.promise.resolve()
                end
            end):catch(function(e)
                log.warn(string.format("修正状态失败 %s", e))
            end)
    end
end

local function app_exit(self, options)
    options = options or blaze.empty

    local final = function(r, j)
        return send_exit_game(self)
        :next(function()
            log.debug(string.format("退出游戏 %s", self.package))
            app.audio.remove_bank(self.package)
            app.i18n.remove(self.package)
            app.i18n.remove("club.threecard.gamelib")
            app.screen.reset()
            return blaze.app.exit(self, true)
        end)
        :next(function()
            for name, def in pairs(self.libs) do
                self[name]:exit()
            end
            return gamelib:exit()
        end)
        :next(r, j)
    end
    return options.instant and blaze.promise.new(final) or self:on_exit():next(final)
end

local function app_on_exit(self)
    return blaze.promise.new(function(resolve, reject)
        if app.entry.entry_game then
            -- 锁游戏，用子游戏自己的弹窗来询问(TODO)，确认退出就直接调大厅的静默退出
            local msgbox = app.game.running.modules["msgbox"] or app.msgbox
            msgbox.show({
                content = app.i18n.text("system:exit alert"),
                confirm_text = app.i18n.text("system:confirm exit"),
                on_confirm = function() app:exit({ instant = true }) end,
                on_cancel = reject,
                cancel_text = app.i18n.text("system:cancel exit")
            })
        else
            resolve()
        end
    end)
end

local function app_module_search(t, name)
    local ret = t.modules[name]
    local ok, result
    local inited = false
    if ret == nil then
        ok, result = true, gamelib[name]
        inited = true
    elseif type(ret) == "table" then
        ok, result = true, ret
    elseif type(ret) == "string" then
        ok, result = pcall(require, ret)
    elseif type(ret) == "function" then
        ok, result = pcall(ret)
    end

    if ok then
        if result == nil then
            error(string.format("%s 模块不存在", name))
        end

        rawset(t, name, result)
        if not inited then
            t.loaded_modules[name] = result
            if result.init then
                result.init(t)
            end
        end

        return result
    else
        error(string.format("加载 %s 模块失败 错误：%s", name, result))
    end
end

-- 创建一个子游戏实例
-- options.package 子游戏包名
function M.new(options)
    assert(options)
    assert(type(options.package) == "string", "子游戏必须包含包名")
    options.libs = options.libs or {}
    options.module_search = app_module_search
    options.gamescene = options.gamescene or {}
    options.start = app_start
    options.on_start = options.on_start or app_on_start
    options.on_started = options.on_started or app_on_started
    options.exit = app_exit
    options.on_exit = options.on_exit or app_on_exit
    options.on_exited = options.on_exited or blaze.noop
    app.store.game.set_game({package = options.package, index = 1})
    options.require = function(relative) return require(options.package .. "." .. relative) end
     
    local ret = blaze.app.new(options)
    -- 兼容游戏内的game.gamelib的写法，不需要重复定义了
    -- 后续可以把这个game.new的逻辑移到大厅里去
    ret.gamelib = gamelib
    return ret
end

return M
